/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.view;

import com.example.android.apis.R;

import android.content.ClipData;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.*;
import android.os.SystemClock;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.DragEvent;
import android.view.View;
import android.widget.TextView;

public class DraggableDot extends View {
	static final String TAG = "DraggableDot";

	private boolean mDragInProgress;
	private boolean mHovering;
	private boolean mAcceptsDrag;
	TextView mReportView;

	private Paint mPaint;
	private TextPaint mLegendPaint;
	private Paint mGlow;
	private static final int NUM_GLOW_STEPS = 10;
	private static final int GREEN_STEP = 0x0000FF00 / NUM_GLOW_STEPS;
	private static final int WHITE_STEP = 0x00FFFFFF / NUM_GLOW_STEPS;
	private static final int ALPHA_STEP = 0xFF000000 / NUM_GLOW_STEPS;

	int mRadius;
	int mAnrType;
	CharSequence mLegend;

	static final int ANR_NONE = 0;
	static final int ANR_SHADOW = 1;
	static final int ANR_DROP = 2;

	void sleepSixSeconds() {
		// hang forever; good for producing ANRs
		long start = SystemClock.uptimeMillis();
		do {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}
		} while (SystemClock.uptimeMillis() < start + 6000);
	}

	// Shadow builder that can ANR if desired
	class ANRShadowBuilder extends DragShadowBuilder {
		boolean mDoAnr;

		public ANRShadowBuilder(View view, boolean doAnr) {
			super(view);
			mDoAnr = doAnr;
		}

		@Override
		public void onDrawShadow(Canvas canvas) {
			if (mDoAnr) {
				sleepSixSeconds();
			}
			super.onDrawShadow(canvas);
		}
	}

	public DraggableDot(Context context, AttributeSet attrs) {
		super(context, attrs);

		setFocusable(true);
		setClickable(true);

		mLegend = "";

		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setStrokeWidth(6);
		mPaint.setColor(0xFFD00000);

		mLegendPaint = new TextPaint();
		mLegendPaint.setAntiAlias(true);
		mLegendPaint.setTextAlign(Paint.Align.CENTER);
		mLegendPaint.setColor(0xFFF0F0FF);

		mGlow = new Paint();
		mGlow.setAntiAlias(true);
		mGlow.setStrokeWidth(1);
		mGlow.setStyle(Paint.Style.STROKE);

		// look up any layout-defined attributes
		TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.DraggableDot);

		final int N = a.getIndexCount();
		for (int i = 0; i < N; i++) {
			int attr = a.getIndex(i);
			switch (attr) {
			case R.styleable.DraggableDot_radius: {
				mRadius = a.getDimensionPixelSize(attr, 0);
			}
				break;

			case R.styleable.DraggableDot_legend: {
				mLegend = a.getText(attr);
			}
				break;

			case R.styleable.DraggableDot_anr: {
				mAnrType = a.getInt(attr, 0);
			}
				break;
			}
		}

		Log.i(TAG, "DraggableDot @ " + this + " : radius=" + mRadius
				+ " legend='" + mLegend + "' anr=" + mAnrType);

		setOnLongClickListener(new View.OnLongClickListener() {
			public boolean onLongClick(View v) {
				ClipData data = ClipData.newPlainText("dot",
						"Dot : " + v.toString());
				v.startDrag(data, new ANRShadowBuilder(v,
						mAnrType == ANR_SHADOW), (Object) v, 0);
				return true;
			}
		});
	}

	void setReportView(TextView view) {
		mReportView = view;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		float wf = getWidth();
		float hf = getHeight();
		final float cx = wf / 2;
		final float cy = hf / 2;
		wf -= getPaddingLeft() + getPaddingRight();
		hf -= getPaddingTop() + getPaddingBottom();
		float rad = (wf < hf) ? wf / 2 : hf / 2;
		canvas.drawCircle(cx, cy, rad, mPaint);

		if (mLegend != null && mLegend.length() > 0) {
			canvas.drawText(mLegend, 0, mLegend.length(), cx,
					cy + mLegendPaint.getFontSpacing() / 2, mLegendPaint);
		}

		// if we're in the middle of a drag, light up as a potential target
		if (mDragInProgress && mAcceptsDrag) {
			for (int i = NUM_GLOW_STEPS; i > 0; i--) {
				int color = (mHovering) ? WHITE_STEP : GREEN_STEP;
				color = i * (color | ALPHA_STEP);
				mGlow.setColor(color);
				canvas.drawCircle(cx, cy, rad, mGlow);
				rad -= 0.5f;
				canvas.drawCircle(cx, cy, rad, mGlow);
				rad -= 0.5f;
			}
		}
	}

	@Override
	protected void onMeasure(int widthSpec, int heightSpec) {
		int totalDiameter = 2 * mRadius + getPaddingLeft() + getPaddingRight();
		setMeasuredDimension(totalDiameter, totalDiameter);
	}

	/**
	 * Drag and drop
	 */
	@Override
	public boolean onDragEvent(DragEvent event) {
		boolean result = false;
		switch (event.getAction()) {
		case DragEvent.ACTION_DRAG_STARTED: {
			// claim to accept any dragged content
			Log.i(TAG, "Drag started, event=" + event);
			// cache whether we accept the drag to return for LOCATION events
			mDragInProgress = true;
			mAcceptsDrag = result = true;
			// Redraw in the new visual state if we are a potential drop target
			if (mAcceptsDrag) {
				invalidate();
			}
		}
			break;

		case DragEvent.ACTION_DRAG_ENDED: {
			Log.i(TAG, "Drag ended.");
			if (mAcceptsDrag) {
				invalidate();
			}
			mDragInProgress = false;
			mHovering = false;
		}
			break;

		case DragEvent.ACTION_DRAG_LOCATION: {
			// we returned true to DRAG_STARTED, so return true here
			Log.i(TAG, "... seeing drag locations ...");
			result = mAcceptsDrag;
		}
			break;

		case DragEvent.ACTION_DROP: {
			Log.i(TAG, "Got a drop! dot=" + this + " event=" + event);
			if (mAnrType == ANR_DROP) {
				sleepSixSeconds();
			}
			processDrop(event);
			result = true;
		}
			break;

		case DragEvent.ACTION_DRAG_ENTERED: {
			Log.i(TAG, "Entered dot @ " + this);
			mHovering = true;
			invalidate();
		}
			break;

		case DragEvent.ACTION_DRAG_EXITED: {
			Log.i(TAG, "Exited dot @ " + this);
			mHovering = false;
			invalidate();
		}
			break;

		default:
			Log.i(TAG, "other drag event: " + event);
			result = mAcceptsDrag;
			break;
		}

		return result;
	}

	private void processDrop(DragEvent event) {
		final ClipData data = event.getClipData();
		final int N = data.getItemCount();
		for (int i = 0; i < N; i++) {
			ClipData.Item item = data.getItemAt(i);
			Log.i(TAG, "Dropped item " + i + " : " + item);
			if (mReportView != null) {
				String text = item.coerceToText(getContext()).toString();
				if (event.getLocalState() == (Object) this) {
					text += " : Dropped on self!";
				}
				mReportView.setText(text);
			}
		}
	}
}